ROOT
Overview
The ROOT function finds solutions to systems of nonlinear equations using SciPy’s scipy.optimize.root solver. Given a set of equations and initial guess values, it determines the variable values where all equations equal zero simultaneously—a fundamental problem in numerical analysis, engineering, and scientific computing.
Root-finding for systems of equations extends the concept of finding zeros of a single function to multiple dimensions. For a system of n equations with n unknowns, the solver seeks a vector \mathbf{x} such that:
\mathbf{F}(\mathbf{x}) = \begin{pmatrix} f_1(x_1, x_2, \ldots, x_n) \\ f_2(x_1, x_2, \ldots, x_n) \\ \vdots \\ f_n(x_1, x_2, \ldots, x_n) \end{pmatrix} = \mathbf{0}
The implementation uses the hybrid Powell method (hybr) by default, which combines the robustness of the steepest descent method with the fast convergence of Newton’s method near the solution. This algorithm, originally implemented in MINPACK, iteratively refines the initial guess using a modified trust-region approach. For more details, see the SciPy optimization documentation.
This function accepts equations as string expressions using standard mathematical notation, including support for common functions like sin, cos, exp, log, and sqrt. The ^ operator is automatically converted to Python’s exponentiation syntax. Variables are named sequentially as x, y, z, x3, x4, and so on, corresponding to the order of initial guess values provided.
Applications include solving equilibrium conditions in chemical systems, intersection of curves and surfaces in computational geometry, steady-state analysis in control systems, and calibration problems in financial modeling.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=ROOT(equations, variables)
equations(list[list], required): 2D list of equation strings expressed in terms of variables (x, y, z, etc.) that should evaluate to zero at the solution.variables(list[list], required): 2D list providing initial guess values for each variable. The number of values must match the number of equations.
Returns (list[list]): 2D list [[x1, x2, …]], or error message string.
Examples
Example 1: Circle and line intersection
Inputs:
| equations | variables | |
|---|---|---|
| x^2 + y^2 - 1 | 0.5 | 0.5 |
| x - y |
Excel formula:
=ROOT({"x^2 + y^2 - 1";"x - y"}, {0.5,0.5})
Expected output:
| Result | |
|---|---|
| 0.70710678 | 0.70710678 |
Example 2: Coupled cubic system
Inputs:
| equations | variables | |
|---|---|---|
| x^3 + y - 1 | 0.7 | 0.7 |
| x + y^3 - 1 |
Excel formula:
=ROOT({"x^3 + y - 1";"x + y^3 - 1"}, {0.7,0.7})
Expected output:
| Result | |
|---|---|
| 0.68260619 | 0.68260619 |
Example 3: Circle and line negative solution
Inputs:
| equations | variables | |
|---|---|---|
| x^2 + y^2 - 1 | -0.5 | -0.5 |
| x - y |
Excel formula:
=ROOT({"x^2 + y^2 - 1";"x - y"}, {-0.5,-0.5})
Expected output:
| Result | |
|---|---|
| -0.70710678 | -0.70710678 |
Example 4: Coupled cubic with different initial guess
Inputs:
| equations | variables | |
|---|---|---|
| x^3 + y - 1 | 0.2 | 0.2 |
| x + y^3 - 1 |
Excel formula:
=ROOT({"x^3 + y - 1";"x + y^3 - 1"}, {0.2,0.2})
Expected output:
| Result | |
|---|---|
| 0.68260619 | 0.68260619 |
Python Code
import math
import re
import numpy as np
from scipy.optimize import root as scipy_root
def root(equations, variables):
"""
Solve a square nonlinear system using SciPy's ``root`` solver.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.root.html
This example function is provided as-is without any representation of accuracy.
Args:
equations (list[list]): 2D list of equation strings expressed in terms of variables (x, y, z, etc.) that should evaluate to zero at the solution.
variables (list[list]): 2D list providing initial guess values for each variable. The number of values must match the number of equations.
Returns:
list[list]: 2D list [[x1, x2, ...]], or error message string.
"""
def to2d(x):
return [[x]] if not isinstance(x, list) else x
equations = to2d(equations)
if not isinstance(equations, list) or len(equations) == 0:
return "Invalid input: equations must be a 2D list of strings."
# Flatten nested equation lists while retaining only string entries.
flattened_equations = []
for row in equations:
if not isinstance(row, list) or len(row) == 0:
return "Invalid input: equations must be a 2D list of strings."
for item in row:
if isinstance(item, str) and item.strip() != "":
flattened_equations.append(item)
if len(flattened_equations) == 0:
return "Invalid input: equations must contain at least one string expression."
# Normalize variables which may be passed as a scalar by the Excel add-in
variables = to2d(variables)
if not isinstance(variables, list) or len(variables) == 0 or not isinstance(variables[0], list):
return "Invalid input: variables must be a 2D list of numeric values."
initial_guess = variables[0]
if len(initial_guess) == 0:
return "Invalid input: variables must contain at least one initial guess value."
try:
x0 = np.asarray([float(value) for value in initial_guess], dtype=float)
except (TypeError, ValueError):
return "Invalid input: variables must contain numeric values."
equation_count = len(flattened_equations)
if x0.size != equation_count:
return (
f"Invalid input: number of variables ({x0.size}) must match number of equations ({equation_count})."
)
# Convert caret to exponentiation once before any processing
flattened_equations = [re.sub(r'\^', '**', eq) for eq in flattened_equations]
# Generate readable variable names (x, y, z, x3, x4, ...)
base_names = ["x", "y", "z"]
variable_names = []
for index in range(equation_count):
if index < len(base_names):
variable_names.append(base_names[index])
else:
variable_names.append(f"x{index}")
# Complete safe_globals dictionary with all math functions and aliases
safe_globals = {
"math": math,
"np": np,
"numpy": np,
"__builtins__": {},
}
safe_globals.update({
name: getattr(math, name)
for name in dir(math)
if not name.startswith("_")
})
safe_globals.update({
"sin": np.sin,
"cos": np.cos,
"tan": np.tan,
"asin": np.arcsin,
"arcsin": np.arcsin,
"acos": np.arccos,
"arccos": np.arccos,
"atan": np.arctan,
"arctan": np.arctan,
"sinh": np.sinh,
"cosh": np.cosh,
"tanh": np.tanh,
"exp": np.exp,
"log": np.log,
"ln": np.log,
"log10": np.log10,
"sqrt": np.sqrt,
"abs": np.abs,
"pow": np.power,
"pi": math.pi,
"e": math.e,
"inf": math.inf,
"nan": math.nan,
})
def _system(vector):
local_context = {name: vector[i] for i, name in enumerate(variable_names)}
residuals = []
for expression in flattened_equations:
try:
value = eval(expression, safe_globals, local_context)
except Exception as exc:
raise ValueError(f"Error evaluating equation: {exc}")
try:
numeric_value = float(value)
except (TypeError, ValueError) as exc:
raise ValueError(f"Equation did not return a numeric value: {exc}")
if not math.isfinite(numeric_value):
raise ValueError("Equation evaluation produced a non-finite value.")
residuals.append(numeric_value)
return np.asarray(residuals, dtype=float)
try:
result = scipy_root(_system, x0=x0)
except ValueError as exc:
return f"root error: {exc}"
except Exception as exc:
return f"root error: {exc}"
if not result.success or result.x is None:
message = result.message if hasattr(result, "message") else "Solver did not converge."
return f"root failed: {message}"
if not np.all(np.isfinite(result.x)):
return "root failed: solution contains non-finite values."
solution = [float(value) for value in result.x]
return [solution]